package com.huahai.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.huahai.common.ErrorCode;
import com.huahai.constant.RedisConstant;
import com.huahai.exception.BusinessException;
import com.huahai.mapper.UserMapper;
import com.huahai.pojo.dto.UserUpdateDTO;
import com.huahai.pojo.entity.User;
import com.huahai.pojo.vo.UserVO;
import com.huahai.service.UserService;
import com.huahai.utils.AlgorithmEditDistanceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math3.util.Pair;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.huahai.contant.UserConstant.ADMIN_ROLE;
import static com.huahai.contant.UserConstant.USER_LOGIN_STATE;

/**
 * 用户服务实现类
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    private static final long RECOMMEND_EXPIRE_TIME_MINUTE = 10;
    @Resource
    private UserMapper userMapper;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    // 默认头像
    private static final String DEFAULT_AVATAR_URL = "https://javawebprogram.oss-cn-beijing.aliyuncs.com/2025/07/96e4337a-ca4d-49bf-be07-43bd3af88f90.webp";
    // 默认用户名
    private static final String DEFAULT_USERNAME = "系统用户";
    // 默认性别
    private static final Integer DEFAULT_GENDER = 0;


    /**
     * 盐值，混淆密码
     */
    private static final String SALT = "huahai";

    @Autowired
    private Gson gson;

    /**
     * 用户注册
     *
     * @param userAccount   用户账户
     * @param userPassword  用户密码
     * @param checkPassword 校验密码
     * @param planetCode    星球编号
     * @return 新用户 id
     */
    @Override
    public long userRegister(String userAccount, String userPassword, String checkPassword, String planetCode) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
        }
        if (userPassword.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
        }
        if (planetCode.length() > 6) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号过长");
        }
        // 账户不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号不能包含特殊字符");
        }
        // 密码和校验密码相同
        if (!userPassword.equals(checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码不一致");
        }

        // 账户不能重复
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        long count = userMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
        }
        // 星球编号不能重复
        queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("planetCode", planetCode);
        count = userMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "编号已存在");
        }
        // 2. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 3. 插入数据
        User user = new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        user.setPlanetCode(planetCode);
        user.setAvatarUrl(DEFAULT_AVATAR_URL);
        user.setUsername(DEFAULT_USERNAME + planetCode);
        user.setGender(DEFAULT_GENDER);
        boolean saveResult = this.save(user);
        if (!saveResult) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败");
        }
        return user.getId();
    }


    /**
     * 用户登录
     *
     * @param userAccount  用户账户
     * @param userPassword 用户密码
     * @param request
     * @return 脱敏后的用户信息
     */
    @Override
    public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号不能少于四位");
        }
        if (userPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码不能少于八位");
        }
        // 账户不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号不能包含特殊字符");
        }
        // 2. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 查询用户是否存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        queryWrapper.eq("userPassword", encryptPassword);
        User user = userMapper.selectOne(queryWrapper);
        // 用户不存在
        if (user == null) {
            log.info("user login failed, userAccount cannot match userPassword");
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名或密码错误");
        }
        // 3. 用户脱敏
        User safetyUser = getSafetyUser(user);
        // 4. 记录用户的登录态
        request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);
        return safetyUser;
    }

    /**
     * 用户脱敏
     *
     * @param originUser
     * @return
     */
    @Override
    public User getSafetyUser(User originUser) {
        if (originUser == null) {
            return null;
        }
        User safetyUser = new User();
        safetyUser.setId(originUser.getId());
        safetyUser.setUsername(originUser.getUsername());
        safetyUser.setUserAccount(originUser.getUserAccount());
        safetyUser.setAvatarUrl(originUser.getAvatarUrl());
        safetyUser.setGender(originUser.getGender());
        safetyUser.setPhone(originUser.getPhone());
        safetyUser.setEmail(originUser.getEmail());
        safetyUser.setPlanetCode(originUser.getPlanetCode());
        safetyUser.setUserRole(originUser.getUserRole());
        safetyUser.setUserStatus(originUser.getUserStatus());
        safetyUser.setCreateTime(originUser.getCreateTime());
        safetyUser.setTags(originUser.getTags());
        safetyUser.setProfile(originUser.getProfile());
        return safetyUser;
    }

    /**
     * 用户注销
     *
     * @param request
     */
    @Override
    public int userLogout(HttpServletRequest request) {
        // 移除登录态
        HttpSession session = request.getSession(false); // 不创建新Session
        if (session != null) {
            // 移除登录态（可选，invalidate会清除所有属性）
            session.removeAttribute(USER_LOGIN_STATE);
            // 使Session失效（会自动同步到Redis并删除）
            session.invalidate();
        }
        return 1;
    }

    /**
     * 根据标签查询用户列表 - and（返回带所有标签的用户）- 内存流查询
     *
     * @param tagNameList
     * @param pageIndex
     * @param pageSize
     * @return
     */
    @Override
    public List<UserVO> queryUsersByTags(List<String> tagNameList, long pageIndex, long pageSize, HttpServletRequest request) {
        // 1. 判断非空
        if (CollectionUtils.isEmpty(tagNameList)) {
            Page<User> userPage = searchUsersRecommend(pageIndex, pageSize, request);
            List<UserVO> userVOList = new ArrayList<>();
            userPage.getRecords().forEach(user -> {
                UserVO userVO = new UserVO();
                BeanUtils.copyProperties(user, userVO);
                userVOList.add(userVO);
            });
            return userVOList;
        }
        // 2.查询所有用户信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        Page<User> userList = this.page(new Page<>(pageIndex, pageSize), queryWrapper);
        Gson gson = new Gson();
        // 3.在内存中查询用户的标签
        // 4.封装返回结果
        return userList.getRecords().stream().filter(user -> {
            // 3.1.获取用户的标签
            String tags = user.getTags();
            if (StringUtils.isBlank(tags)) {
                return false;
            }
            // 3.2.将得到的json字符串进行反序列化
            Set<String> userTagNameSet = gson.fromJson(tags, new TypeToken<Set<String>>() {
            }.getType());
            // 利用java8特性optional判断非空，减少代码复杂度
            userTagNameSet = Optional.ofNullable(userTagNameSet).orElse(new HashSet<>());
            for (String tagName : tagNameList) {
                // 不存在，直接返回false
                if (!userTagNameSet.contains(tagName)) {
                    return false;
                }
            }
            return true;
        }).map(user -> {
            UserVO userVO = new UserVO();
            BeanUtils.copyProperties(user, userVO);
            return userVO;
        }).collect(Collectors.toList());
    }

    /**
     * 获取当前登录用户
     *
     * @param request
     * @return
     */
    @Override
    public User getCurrentUser(HttpServletRequest request) {
        // 判断当前请求是否为空
        if (request == null) {
            return null;
        }
        // 判断当前登陆用户是否不存在
        User currentUser = (User) request.getSession().getAttribute(USER_LOGIN_STATE);
        if (currentUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN);
        }
        return currentUser;
    }

    /**
     * 更新用户信息
     *
     * @param userUpdateDTO
     * @param currentUser
     * @return
     */
    @Override
    public int updateUserInfo(UserUpdateDTO userUpdateDTO, User currentUser) {
        // 1. 基础校验（保持不变）
        long userId = userUpdateDTO.getId();
        if (userId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        if (!isAdmin(currentUser) && currentUser.getId() != userId) {
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        User oldUser = userMapper.selectById(userId);
        if (oldUser == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在");
        }

        // 2. 获取需要更新的字段（返回Map，key=数据库字段名，value=值）
        Map<String, Object> updateFields = getUpdateFields(userUpdateDTO);
        if (updateFields.isEmpty()) {
            // 无更新字段，直接返回
            return 0;
        }

        // 3. 使用UpdateWrapper明确构建更新语句
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", userId);
        // 逐个设置需要更新的字段
        for (Map.Entry<String, Object> entry : updateFields.entrySet()) {
            updateWrapper.set(entry.getKey(), entry.getValue());
        }

        // 4. 执行更新
        return userMapper.update(null, updateWrapper);
    }

    /**
     * 提取需要更新的字段（返回数据库字段名和值的映射）
     */
    private Map<String, Object> getUpdateFields(UserUpdateDTO userUpdateDTO) {
        Map<String, Object> updateFields = new HashMap<>();

        // 处理用户名
        if (StringUtils.isNotBlank(userUpdateDTO.getUsername())) {
            updateFields.put("username", userUpdateDTO.getUsername());
        }

        // 处理头像
        if (StringUtils.isNotBlank(userUpdateDTO.getAvatarUrl())) {
            updateFields.put("avatarUrl", userUpdateDTO.getAvatarUrl());
        }

        // 处理性别
        Integer gender = userUpdateDTO.getGender();
        if (gender != null && (gender == 0 || gender == 1)) {
            updateFields.put("gender", gender);
        }

        // 处理手机号
        if (StringUtils.isNotBlank(userUpdateDTO.getPhone())) {
            updateFields.put("phone", userUpdateDTO.getPhone());
        }

        // 处理邮箱
        if (StringUtils.isNotBlank(userUpdateDTO.getEmail())) {
            updateFields.put("email", userUpdateDTO.getEmail());
        }

        // 处理标签
        if (StringUtils.isNotBlank(userUpdateDTO.getTags())) {
            updateFields.put("tags", userUpdateDTO.getTags());
        }

        // 处理密码（核心校验逻辑）
        String newPassword = userUpdateDTO.getNewPassword();
        String checkPassword = userUpdateDTO.getCheckPassword();
        String oldPasswordInput = userUpdateDTO.getUserPassword();
        if (!StringUtils.isAllBlank(newPassword, checkPassword, oldPasswordInput)) {
            // 校验密码不能为空
            if (Objects.equals(newPassword, "") ||
                    Objects.equals(oldPasswordInput, "") ||
                    Objects.equals(checkPassword, "")) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码不能为空");
            }
            // 校验新密码一致性
            if (!newPassword.equals(checkPassword)) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次密码不一致");
            }
            // 校验密码长度
            if (newPassword.length() < 8) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度过短");
            }
            // 加密新密码
            String encryptedNewPassword = DigestUtils.md5DigestAsHex((SALT + newPassword).getBytes());
            // 获取数据库中的旧密码
            User loginUser = userMapper.selectById(userUpdateDTO.getId());
            String dbOldPassword = loginUser.getUserPassword();
            // 校验旧密码（首次设置密码时dbOldPassword为null）
            if (dbOldPassword != null) {
                String encryptedOldInput = DigestUtils.md5DigestAsHex((SALT + oldPasswordInput).getBytes());
                if (!encryptedOldInput.equals(dbOldPassword)) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "旧密码错误");
                }
                // 校验新旧密码是否相同
                if (encryptedNewPassword.equals(dbOldPassword)) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "新旧密码不能相同");
                }
            }
            // 密码校验通过，加入更新字段
            updateFields.put("userPassword", encryptedNewPassword);
        }

        return updateFields;
    }

    /**
     * 根据标签查询用户列表 - and（返回带所有标签的用户）- SQL查询
     *
     * @param tagNameList
     * @return
     */
    @Deprecated // 已弃用
    public List<User> queryUsersByTagsBySQL(List<String> tagNameList) {
        // 1. 判断非空
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        // 2. 创建查询条件
        // sql : like "%java%" and "%python%" and "%go%"
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        for (String tagName : tagNameList) {
            queryWrapper = queryWrapper.like("tags", tagName);
        }
        // 3. 查询用户列表
        List<User> userList = userMapper.selectList(queryWrapper);
        // 4. 封装返回结果
        // 获取脱敏后的用户信息
        return userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
    }

    /**
     * 是否为管理员
     *
     * @param request
     * @return
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {
        // 仅管理员可查询
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User user = (User) userObj;
        return user != null && user.getUserRole() == ADMIN_ROLE;
    }

    /**
     * 主页推荐用户信息 - 使用redis
     *
     * @param pageIndex
     * @param pageSize
     * @param request
     * @return
     */
    @Override
    public Page<User> searchUsersRecommend(long pageIndex, long pageSize, HttpServletRequest request) {
        // 1.判断当前登陆用户是否不存在
        // 默认未登录的用户也可以查看主页，但不能使用心动模式
        Page<User> userList = new Page<>(pageIndex, pageSize);
        User currentUser = null;
        try {
            currentUser = getCurrentUser(request);
        } catch (Exception ignore) {
        }

        String key;
        // 生成包含分页参数的缓存键（核心修复）
        if (currentUser != null) {
            key = String.format(
                    RedisConstant.PROJECT_KEY + RedisConstant.USER_KEY + "recommend:%s:%d:%d",
                    currentUser.getId(),
                    pageIndex,  // 加入 pageIndex
                    pageSize    // 加入 pageSize
            );
        } else {
            key = String.format(
                    RedisConstant.PROJECT_KEY + RedisConstant.USER_KEY + "recommend:anonymous:%d:%d",
                    pageIndex,  // 加入 pageIndex
                    pageSize    // 加入 pageSize
            );
        }

        // 2.查询缓存中是否存在
        Page<User> pageUserList = (Page<User>) redisTemplate.opsForValue().get(key);
        // 3.存在直接返回
        if (pageUserList != null) {
            return pageUserList;
        }
        // 4.不存在，查询数据库
        // 内部会调用redisTemplate.set(...) 自动写入缓存
        userList = recommendQuery(pageIndex, pageSize, key);
        // 5.返回结果
        return userList;
    }

    /**
     * 推荐查询数据库
     *
     * @param pageIndex
     * @param pageSize
     * @param key
     * @return
     */
    private Page<User> recommendQuery(long pageIndex, long pageSize, String key) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        Page<User> userList = this.page(new Page<>(pageIndex, pageSize), queryWrapper);
        try {
            redisTemplate.opsForValue().set(key, userList, RECOMMEND_EXPIRE_TIME_MINUTE, TimeUnit.MINUTES);
        } catch (Exception e) {
            log.error("redis setValue error:", e);
        }
        return userList;
    }

    /**
     * 获取前num条匹配的用户信息推荐
     *
     * @param num
     * @param loginUser
     * @return
     */
    @Override
    public List<UserVO> matchesTopNum(Long num, User loginUser) {
        // 优化 1. 剔除自身
        //     2. 只查询需要的列名
        //     3. 过滤掉标签为空的用户
        // 1. 遍历所有用户
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        // 过滤掉标签为空的用户
        userQueryWrapper.isNotNull("tags");
        // 只查询需要的列名
        userQueryWrapper.select("id", "tags");
        List<User> userList = this.list(userQueryWrapper);
        // 2. 获取当前登录用户的标签
        String loginTags = loginUser.getTags();
        // 2.1 格式化标签为json字符串
        Gson gson = new Gson();
        List<String> loginTagStr = gson.fromJson(loginTags, new TypeToken<List<String>>() {
        }.getType());
        // 3. 获取所有用户的标签
        List<Pair<User, Long>> userDistanceList = new ArrayList<>();
        for (int i = 0; i < userList.size(); i++) {
            User user = userList.get(i);
            String userTags = user.getTags();
            // 剔除自身
            if (StringUtils.isBlank(userTags) || user.getId().equals(loginUser.getId())) {
                continue;
            }
            // 3.1 格式化标签为json字符串
            List<String> userTagsStr = gson.fromJson(userTags, new TypeToken<List<String>>() {
            }.getType());
            // 4. 利用编辑距离算法计算标签的编辑距离（相似度）
            long distance = AlgorithmEditDistanceUtil.minDistanceTagList(userTagsStr, loginTagStr);
            userDistanceList.add(new Pair<>(user, distance));
        }
        // 5. 遍历集合，获取前num条匹配的用户信息, 按照编辑距离降序排序
        List<Pair<User, Long>> sortedUserList = userDistanceList.stream().sorted((pair1, pair2) -> {
            return (int) (pair1.getValue() - pair2.getValue());
        }).limit(num).collect(Collectors.toList());
        // 5.1 获取用户排序后的id列表
        List<Long> sortedIdList = sortedUserList.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList());

        // 6. 根据获取到的id查询用户信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("id", sortedIdList);
        List<User> unSortedUserList = this.list(queryWrapper);
        // 6.1 返回前num条匹配的用户信息
        Map<Long, List<UserVO>> userVOMap = unSortedUserList.stream().map(user -> {
            UserVO userVO = new UserVO();
            User safetyUser = getSafetyUser(user);
            BeanUtils.copyProperties(safetyUser, userVO);
            return userVO;
        }).collect(Collectors.groupingBy(UserVO::getId));
        // 6.2 通过原有的排序方式排序数据并返回（in关键字的缺陷）
        List<UserVO> finalUserVOList = new ArrayList<>();
        for (Long userId : sortedIdList) {
            finalUserVOList.add(userVOMap.get(userId).get(0));
        }
        return finalUserVOList;
    }


    /**
     * 是否为管理员
     *
     * @param user
     * @return
     */
    @Override
    public boolean isAdmin(User user) {
        // 仅管理员可查询
        if (user == null) {
            return false;
        }
        Integer userRole = user.getUserRole();
        // 仅管理员可查询
        return userRole != null && userRole == ADMIN_ROLE;
    }


}

